using System;
using System.Collections;
using System.Reflection;
using System.Threading;
using System.Web;
using log4net;

namespace Sedna.Core.Controller
{
	public class DefaultActionExecutor : IActionExecutor
	{

		private const string defaultActionName="default";

		private ILog log = LogManager.GetLogger(typeof(DefaultActionExecutor));

		private IAction defaultAction;
		private IExceptionHandlerMatcher exceptionHandlerMatcher;

		public IAction DefaultAction
		{
			set { defaultAction = value; }
		}

		public IExceptionHandlerMatcher ExceptionHandlerMatcher
		{
			set { exceptionHandlerMatcher = value; }
        }

        #region Execute methods

        public object ExecuteAction(IExecutionContext executionContext, string currentScopePrefix)
		{
			return ExecuteAction(executionContext, null);
		}


        public object ExecuteAction(IExecutionContext executionContext, IConfiguration configuration, string currentScopePrefix)
		{
            return ExecuteAction(executionContext, configuration, currentScopePrefix, defaultActionName);
		}


        public object ExecuteAction(IExecutionContext executionContext, IConfiguration configuration, string currentScopePrefix, string actionName)
		{
            SetUpInfra(configuration, executionContext, actionName, currentScopePrefix);

            IAction action = LookupAction(configuration, actionName);
			if(action == null || !action.CanExecute(executionContext))
			{
				return executionContext.CallContext.Collapse();
			}

			IActionResult result = null;
            
			try
			{
			    log.DebugFormat("Executing action '{0}' of type {1} on object '{2}' of type {3}. Current scope prefix {4}.", actionName, action.GetType(), configuration.ObjectName, configuration.GetType(), executionContext.CurrentScopePrefix);
				result = action.Execute(executionContext);
			    if(actionName == result.Status)
			    {
				    log.Equals("action chain cycle detected. actionName=" + actionName + "; configuration : " + (configuration!=null?configuration.ObjectName:"undefined"));
				    throw new ControllerConfigurationException("action chain cycle detected. actionName=" + actionName + "; configuration : " + (configuration!=null?configuration.ObjectName:"undefined"));
			    }
			    return ExecuteAction(executionContext, configuration, currentScopePrefix, result.Status);
			}
			catch(Exception exAction)
			{
			    if (log.IsDebugEnabled)
			    {
                    log.Debug(String.Format("Exception occured while trying to execute an action name={0}, in the configuration={1}", (actionName != null ? actionName : defaultActionName), (configuration != null ? configuration.ToString() : "null")), exAction);
                }
			    return HandleException(executionContext, configuration, WrapException(exAction, action));
			}

        }

	    private void SetUpInfra(IConfiguration configuration, IExecutionContext executionContext, string actionName, string currentScopePrefix)
	    {
	        if(configuration != null)
	        {
	            executionContext["infrastructure.CurrentConfiguration"] = configuration;
	            executionContext["infrastructure.CurrentConfiguration.ObjectName"] = configuration.ObjectName;
	        }

	        executionContext["infrastructure.CurrentScopePrefix"] = currentScopePrefix;
	        executionContext["infrastructure.ActionName"] = actionName;
	    }

	    private Exception WrapException(Exception exception, IAction action)
	    {
            if (action.ErrorCode > 0 && !(exception is SednaException))
            {
                return new SednaException(action.ErrorCode, exception);
            }
            return exception;
        }
	    
	    public object HandleException(IExecutionContext executionContext, IConfiguration configuration, Exception exAction)
	    {
	        IActionResult result;
	        if(exAction is System.StackOverflowException 
	           || exAction is System.ExecutionEngineException 
	           || exAction is System.OutOfMemoryException)
	        {
	            throw exAction;
	        }

//	        if(exAction is TargetInvocationException)
//	        {
//	            Exception innerException = ((TargetInvocationException)exAction).InnerException;
//	            exAction = innerException!=null?innerException:exAction;
//	        }


	        IExceptionHandler handler = LookupExceptionHandler(configuration, exAction);
	        if(handler == null)
	        {
	            log.Warn(String.Format("Unable to locate handler for exception in configuration={0}", (configuration!=null?configuration.ToString():"null")), exAction);
	            throw exAction;
	        }
	        
	        try
	        {
	            result = handler.HandleException(executionContext, exAction);
	        }
	        catch(Exception exHandler)//normally an IExceptionHandler should not throw an Exception, though if really something serious happens, which should be considered as an error - it is possible for it to throw, this exception will be logged and propagated upwards. it might be wise still to catch it somewhere and to show a user an error page instead of a stacktrace, but that is once again - not a typical case - all exceptions should be catched gracefully and redirected to a default exception page
	        {
	            if(exHandler is System.StackOverflowException 
	               || exHandler is System.ExecutionEngineException 
	               || exHandler is System.OutOfMemoryException)
	            {
	                throw;
	            }
					
	            log.Error("exception handler has thrown an exception", exHandler);
	            throw;
	        }
	        
	        return ExecuteAction(executionContext, configuration, executionContext.CurrentScopePrefix, result.Status);
	    }

	    #endregion

        #region Private methods

        private IAction LookupAction(IConfiguration configuration, string actionName)
		{
			IAction action = null;
			if(configuration != null)
			{
				action = configuration.GetAction(actionName);
			}
			if(action != null)
			{
				return action;
			} 

			//This causes problems if actionName is "default", and there is no such an action in config.
			//In this case default.aspx is got instead of action.
//			action = (IAction)(SpringContextFactory.GetContext().ContainsObject(actionName)
//				?SpringContextFactory.GetContext().GetObject(actionName)
//				:null);

			if(action != null)
			{
				return action;
			}

			return defaultAction;//can be null - in this case - end a transition
		}

		private IExceptionHandler LookupExceptionHandler(IConfiguration configuration, Exception exception)
		{
			IExceptionHandler handler = null;
			if(configuration != null)
			{
				handler = configuration.GetExceptionHandler(exception);
			}
			if(handler == null && exceptionHandlerMatcher != null)
			{
				handler = exceptionHandlerMatcher.Match(exception);
			}
			return handler;//can be null - in this case just propage the exception

        }

        #endregion

    }
}
